﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024 LiLong                                                   **
**  This file is part of RinexFileReader.                                       **
**                                                                              **
**  RinexFileReader is free software: you can redistribute it and/or modify     **
**  it under the terms of the GNU General Public License as published by        **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  RinexFileReader is distributed in the hope that it will be useful,          **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU General Public License for more details.                                **
**                                                                              **
**  You should have received a copy of the GNU General Public License           **
**  along with RinexFileReader. If not, see <https://www.gnu.org/licenses/>.    **
**********************************************************************************/
#pragma once

#include "../Datadefine.h"

#include <tao/pegtl.hpp>

#include <charconv>
#include <ranges>
#include <string>
#include <string_view>

namespace Rinex
{
namespace PEG
{
using namespace tao::pegtl;
// clang-format off
struct Space : star<one<' '>> {};
struct Float : seq<opt<one<'-'>>, plus<digit>, one<'.'>, plus<digit>> {};
struct Int : seq<opt<one<'-'>>, plus<digit>> {};
struct Year : plus<digit> {};
struct Month : plus<digit> {};
struct Day : plus<digit> {};
struct Hour : plus<digit> {};
struct Minute : plus<digit> {};
struct Second : plus<digit> {};
struct EPOCH : seq<Space, Year, Space, Month, Space, Day, Space, Hour, Space, Minute, Space, Second> {};
using LineTail = sor<string<'\r','\n'>, one<'\n'>>;
// clang-format on
#define RINEX_KEYWORDS(x) seq<TAO_PEGTL_STRING(x), Space, LineTail>

inline std::string_view trim(std::string_view str)
{
    auto notSpace = [](char ch) { return ch != ' '; };
    auto begin    = std::ranges::find_if(str, notSpace);
    if (begin == str.end())
        return {};
    auto end = std::ranges::find_if(str | std::views::reverse, notSpace).base();
    return { begin, end };
}

inline double stod(std::string_view str)
{
    double value;
    auto result = std::from_chars(str.data(), str.data() + str.size(), value);
    if (result.ec == std::errc())
        return value;
    throw std::system_error(std::make_error_code(result.ec));
}

inline int stoi(std::string_view str)
{
    int value;
    auto result = std::from_chars(str.data(), str.data() + str.size(), value);
    if (result.ec == std::errc())
        return value;
    throw std::system_error(std::make_error_code(result.ec));
}

template<typename ParseInput>
inline void consumeTail(ParseInput& in)
{
    if (in.size() >= 1)
    {
        if (*in.current() == '\n')
            in.bump(1);
    }
    if (in.size() >= 2)
    {
        if (std::string_view(in.current(), 2) == "\r\n")
            in.bump(2);
    }
}

inline SatelliteSystem toSatelliteSystem(std::string_view str)
{
    auto c = str.at(0);
    switch (c)
    {
    case 'G':
        return SatelliteSystem::GPS;
    case 'R':
        return SatelliteSystem::GLONASS;
    case 'E':
        return SatelliteSystem::Galileo;
    case 'J':
        return SatelliteSystem::QZSS;
    case 'C':
        return SatelliteSystem::BDS;
    case 'I':
        return SatelliteSystem::NavIC;
    case 'S':
        return SatelliteSystem::SBASPayload;
    case 'M':
        return SatelliteSystem::Mixed;
    default:
        throw std::runtime_error("Unknown satellite system");
    }
}

inline SystemTimeIdentifier toSystemTimeIdentifier(std::string_view str)
{
    static auto list = { "GPS", "GLO", "GAL", "QZS", "BDT", "IRN" };
    if (auto it = std::ranges::find(list, str); it != list.end())
    {
        return static_cast<SystemTimeIdentifier>(std::distance(list.begin(), it));
    }
    throw std::runtime_error("Unknown system time identifier");
}

template<typename T>
struct IntHelper
{
    template<typename ParseInput, typename Data>
    static void apply(const ParseInput& input, Data& p)
    {
        T::setValue(p, stoi(std::string_view(input.begin(), input.size())));
    }
};

template<typename T>
struct DoubleHelper
{
    template<typename ParseInput, typename Data>
    static void apply(const ParseInput& input, Data& p)
    {
        T::setValue(p, stod(std::string_view(input.begin(), input.size())));
    }
};

} // namespace PEG
} // namespace Rinex